package jxtend.processor.eclipse.visitor.interceptor;

import java.util.HashMap;
import java.util.Map;

import jxtend.processor.eclipse.ScopeUtils;
import jxtend.processor.eclipse.utils.ASTNodeUtils;
import jxtend.processor.eclipse.visitor.ASTNodeScopeFactory;
import jxtend.processor.reflect.ReflectField;

import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Literal;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.Scope;

@SuppressWarnings("restriction")
public class CreateSubscopesASTNodeVisitorInterceptor extends ASTNodeVisitorInterceptorAdapter {

	private final Map<Scope, Scope> createdScopes = new HashMap<Scope, Scope>(); 
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private static final Class<ASTNode> classOfAbstractMethodDeclaration =
		(Class) org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration.class;
	
	@Override
	public boolean interceptVisit(ASTNodeVisitorInterceptorContext interceptorChain, ASTNode node, Scope scope) {
//		boolean result = super.interceptVisit(interceptorChain, node, scope);

		if (scope != null) {
			Class<ASTNode> nodeClass = ASTNodeUtils.getClassForNormalKindASTNode(node);
			if (node instanceof AbstractMethodDeclaration) {
				nodeClass = classOfAbstractMethodDeclaration;
			}

			ReflectField<ASTNode, Scope> scopeField = ReflectField.findField(nodeClass, Scope.class, "scope");
			if (scopeField == null) {
				return super.interceptVisit(interceptorChain, node, scope);
			}

			Scope nodeScope = scopeField.get(node);
			if (nodeScope == null) {

//				if (node instanceof Block && ((Block) node).explicitDeclarations != 0) {
					nodeScope = ASTNodeScopeFactory.create(node, scopeField, scope);
					
					if (nodeScope instanceof ClassScope
							&& ((ClassScope) nodeScope).referenceContext.binding != null
							&& ((ClassScope) nodeScope).referenceContext.binding.isAnonymousType()) {
						ASTNode parentOfParentNode = interceptorChain.getParentNode(1);
						if (parentOfParentNode instanceof FieldDeclaration
								&& ((FieldDeclaration) parentOfParentNode).initialization
										== ((ClassScope) nodeScope).referenceContext.allocation
								&& ((FieldDeclaration) parentOfParentNode).initialization.constant == null
								&& ((FieldDeclaration) parentOfParentNode).initialization instanceof Literal) {
							((Literal) ((FieldDeclaration) parentOfParentNode).initialization).computeConstant();
							
	//							&& ((FieldDeclaration) parentOfParentNode).isStatic()) {
	//						((ClassScope) nodeScope).referenceContext.binding.modifiers |= ClassFileConstants.AccStatic;
	
	//						((ClassScope) nodeScope).referenceContext.modifiers |= ClassFileConstants.AccStatic;
						}
					}
//				} else {
//					nodeScope = scope;
//				}

				if (nodeScope == null) {
					return false;
				}

				scopeField.set(node, nodeScope);

				if (nodeScope != scope) {
					createdScopes.put(nodeScope, ScopeUtils.cloneScope(nodeScope));
				}
			}

//			if (node instanceof AbstractMethodDeclaration) {
//				scope = nodeScope;
//			}
		}

//		return result;
		return super.interceptVisit(interceptorChain, node, scope);
	}

	@Override
	public void interceptEndVisit(ASTNodeVisitorInterceptorContext interceptorChain, ASTNode node, Scope scope) {
		super.interceptEndVisit(interceptorChain, node, scope);

		Class<ASTNode> nodeClass = ASTNodeUtils.getClassForNormalKindASTNode(node);
		if (node instanceof AbstractMethodDeclaration) {
			nodeClass = classOfAbstractMethodDeclaration;
		}

		ReflectField<ASTNode, Scope> scopeFieldOfNode = ReflectField.findField(nodeClass, Scope.class, "scope");
		if (scopeFieldOfNode == null) {
			return;
		}

		Scope nodeScope = scopeFieldOfNode.get(node);
		if ((nodeScope != null) && createdScopes.containsKey(nodeScope)) {
			scopeFieldOfNode.set(node, null);

			Scope clonedScope = createdScopes.remove(nodeScope);
			ScopeUtils.copyProperties(clonedScope, nodeScope);
		}
	}

}
